﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Nntp
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;

    /// <summary>Provides helper methods for the implementation of NNTP commands.</summary>
    internal static class CommandHelper
    {
        /// <summary>Returns a value indicating whether the response starting with
        /// <paramref name="initialResponseLineWords"/> is multi-line or not.</summary>
        /// <returns><c>true</c> if the element at index 0 of <paramref name="initialResponseLineWords"/> is equal to
        /// one of the following: "100", "101", "211", "215", "220", "221", "222", "224", "225", "230", "231";
        /// <c>false</c> otherwise.</returns>
        internal static bool IsMultilineResponse(IList<string> initialResponseLineWords)
        {
            switch (initialResponseLineWords[0])
            {
                case "100":
                case "101":

                // According to http://www.ietf.org/rfc/rfc3977.txt, Appendix C, this response code is generated by both
                // the LISTGROUP and the GROUP command. A multi-line data block only follows if a LISTGROUP command was
                // issued. A response to a GROUP command is never multi-line, so the SelectGroup command simply
                // overrides to always return false
                case "211":
                case "215":
                case "220":
                case "221":
                case "222":
                case "224":
                case "225":
                case "230":
                case "231":
                    return true;
                default:
                    // All other response codes indicate single-line responses
                    return false;
            }
        }

        /// <summary>Throws an appropriate exception if <paramref name="initialResponseLineWords"/> indicates that the
        /// associated command failed.</summary>
        /// <remarks>Throws the indicated exception if the element at index 0 of
        /// <paramref name="initialResponseLineWords"/> matches one of the standard response codes in the following
        /// list:
        /// <list type="bullet">
        /// <item>"400": <see cref="ServiceUnavailableException"/></item>
        /// <item>"403": <see cref="ServerFaultException"/></item>
        /// <item>"411": <see cref="NoSuchGroupException"/></item>
        /// <item>"412": <see cref="NoGroupSelectedException"/></item>
        /// <item>"420": <see cref="InvalidCurrentArticleNumberException"/></item>
        /// <item>"421": <see cref="NoNextArticleException"/></item>
        /// <item>"422": <see cref="NoPreviousArticleException"/></item>
        /// <item>"423": <see cref="NoSuchArticleException"/></item>
        /// <item>"430": <see cref="NoSuchArticleException"/></item>
        /// <item>"440": <see cref="PostingNotPermittedException"/></item>
        /// <item>"441": <see cref="PostingFailedException"/></item>
        /// <item>"480": <see cref="MissingAuthenticationException"/></item>
        /// <item>"481": <see cref="AuthenticationFailedException"/></item>
        /// <item>"483": <see cref="MissingPrivacyException"/></item>
        /// <item>"500": <see cref="UnknownCommandException"/></item>
        /// <item>"502": <see cref="CommandNotPermittedException"/></item>
        /// <item>"503": <see cref="FeatureNotSupportedException"/></item>
        /// </list>
        /// </remarks>
        internal static void ThrowOnError(IList<string> initialResponseLineWords)
        {
            ExceptionHelper helper = new ExceptionHelper(initialResponseLineWords);

            // See http://www.ietf.org/rfc/rfc3977.txt, Appendix C
            switch (initialResponseLineWords[0])
            {
                case "400":
                    throw helper.CreateException<ServiceUnavailableException>();
                case "401":
                    // Right after connecting we make sure that we're in reader mode. Transit mode is not supported by
                    // this library so we should never see this response code --> UnexpectedResponseException will be
                    // thrown if we do
                    break;
                case "403":
                    throw helper.CreateException<ServerFaultException>();
                case "411":
                    throw helper.CreateException<NoSuchGroupException>();
                case "412":
                    throw helper.CreateException<NoGroupSelectedException>();
                case "420":
                    throw helper.CreateException<InvalidCurrentArticleNumberException>();
                case "421":
                    throw helper.CreateException<NoNextArticleException>();
                case "422":
                    throw helper.CreateException<NoPreviousArticleException>();
                case "423":
                case "430":
                    throw helper.CreateException<NoSuchArticleException>();
                case "435":
                case "436":
                case "437":
                    // This library does not support transit mode so we should never see these codes -->
                    // UnexpectedResponseException will be thrown if we do
                    break;
                case "440":
                    throw helper.CreateException<PostingNotPermittedException>();
                case "441":
                    throw helper.CreateException<PostingFailedException>();
                case "480":
                    throw helper.CreateException<MissingAuthenticationException>();
                case "481":
                    throw helper.CreateException<AuthenticationFailedException>();
                case "482":
                    // This would be a bug in the library -> UnexpectedResponseException is suitable
                    break;
                case "483":
                    throw helper.CreateException<MissingPrivacyException>();
                case "500":
                    throw helper.CreateException<UnknownCommandException>();
                case "501":
                    // If we see this code then we most probably have a bug in the library -->
                    // UnexpectedResponseException is suitable
                    break;
                case "502":
                    // The following exception must only be thrown for commands other than the initial connection, which
                    // is why ConnectCommand overrides accordingly.
                    throw helper.CreateException<CommandNotPermittedException>();
                case "503":
                    throw helper.CreateException<FeatureNotSupportedException>();
                case "504":
                    // If we see this code then we most probably have a bug in the library -->
                    // UnexpectedResponseException is suitable
                    break;
                default:
                    // All other codes do not indicate an error/failure
                    break;
            }
        }

        /// <summary>Asserts that <paramref name="value"/> is not <c>null</c> and does not contain white space.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="value"/> equals <c>null</c>.</exception>
        /// <exception cref="ArgumentException"><paramref name="value"/> contains white space.</exception>
        internal static void AssertContainsNoWhiteSpace(string parameterName, string value)
        {
            if (value == null)
            {
                throw new ArgumentNullException(parameterName);
            }

            for (int index = 0; index < value.Length; ++index)
            {
                if (char.IsWhiteSpace(value[index]))
                {
                    throw new ArgumentException("Must not contain white space.", parameterName);
                }
            }
        }

        /// <summary>Creates a new stream with <paramref name="createCommandStreamCallback"/>, writes the UTF-8-encoded
        /// <paramref name="command"/> into it and calls <see cref="Stream.Dispose()"/>.</summary>
        internal static void WriteSingleLineCommand(
            CreateCommandStreamCallback createCommandStreamCallback, string command)
        {
            var commandBytes = Encoding.UTF8.GetBytes(command);

            using (var stream = createCommandStreamCallback(false))
            {
                stream.Write(commandBytes, 0, commandBytes.Length);
            }
        }
    }
}
